Skip to content

2777. 日期范围生成器

2777. 日期范围生成器

ts
function* dateRangeGenerator(
  start: string,
  end: string,
  step: number
): Generator<string> {
  const startTime = Date.parse(start)
  const endTime = Date.parse(end)

  if (isNaN(startTime) || isNaN(endTime)) {
    throw new Error('Invalid start or end date')
  }

  if (step <= 0) {
    throw new Error('Step must be greater than 0')
  }

  const ONE_DAY = 86400000 // 24 * 60 * 60 * 1000
  const stepTime = step * ONE_DAY

  for (let time = startTime; time <= endTime; time += stepTime) {
    const d = new Date(time)
    const y = d.getFullYear()
    const m = (d.getMonth() + 1).toString().padStart(2, '0')
    const day = d.getDate().toString().padStart(2, '0')
    yield `${y}-${m}-${day}`
  }
}
/**
 * const g = dateRangeGenerator('2023-04-01', '2023-04-04', 1);
 * g.next().value; // '2023-04-01'
 * g.next().value; // '2023-04-02'
 * g.next().value; // '2023-04-03'
 * g.next().value; // '2023-04-04'
 * g.next().done; // true
 */

时间戳 vs 日期对象

这个实现以毫秒级时间戳为中间表示,for 循环中每次加 step * ONE_DAY 毫秒。这种做法的优点是不需要处理月份跨度和闰年——时间戳的加法天然处理了这些。

但也有一个隐患:夏令时。如果日期范围跨越 DST 切换日,一天可能不是 86400000 毫秒(23 或 25 小时)。这种情况下,new Date(time) 可能产生意料之外的日期。不过对于大多数场景(尤其是只关心日期不关心时间的需求),这个实现已经足够。

惰性生成

Generator 的关键优势:不需要预先生成整个数组。如果日期范围是一整年,step=1 会生成 365 个日期字符串。用 Generator 可以在遍历到某一天时再计算,不必一次性分配 365 个字符串的内存。

错误抛出时机

注意 throw 在 Generator 函数体的顶部——它们惰性执行。创建 Generator 对象(调用 dateRangeGenerator(...))不会执行函数体,只有第一次 .next() 才会触发参数校验。这意味着错误不会在创建 Generator 时立即抛出,而是在实际迭代时。这和普通函数的错误处理直觉不同。